<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="start">Start</button> 
  <button id="cancel">Cancel</button>

  <script>
    /* 取消promise */
    class CancelToken {
      constructor(cancelFn) {
        this.promise = new Promise((resolve, reject) => {
          cancelFn(() => {
            setTimeout(console.log, 0, "delay cancelled")
            resolve()
          })
        })
      }
    }

    const startButton = document.getElementById('start')
    const cancelButton = document.getElementById('cancel')

    function cancellableDelayedResolve(delay) {
      setTimeout(console.log, 0, 'set delay')

      return new Promise((resolve, reject) => {
        const id = setTimeout(() => {
          setTimeout(console.log, 0, 'delayed resolve')
          resolve()
        }, delay)
        const cancelToken = new CancelToken((cancelCallback) => 
          cancelButton.addEventListener('click', cancelCallback)
        )
      })
    }

    startButton.addEventListener('click', () => cancellableDelayedResolve(1000))

    /* 进度通知 */
    class TrackablePromise extends Promise {
      constructor(executor){
        const notifyHandlers = []
        super((resolve, reject) => {
          return executor(resolve, reject, status => {
            notifyHandlers.map(handler => handler(status))
          })
        })
        this.notifyHandlers = notifyHandlers
      }

      notify(notifyHandler) {
        this.notifyHandlers.push(notifyHandler)
        return this
      }
    }

    const p = new TrackablePromise((resolve, reject, notify) => {
      function countdown(x) {
        if (x > 0) {
          notify(`${20 * x}% remaining`)
          setTimeout(() => countdown(x - 1), 1000)
        } else {
          resolve()
        }
      }
      countdown(5)
    })

    p.notify(x => setTimeout(console.log, 0, 'progress:', x))
    p.then(() => setTimeout(console.log, 0, 'completed'))
  </script>
</body>
</html>